﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;

namespace CoreLibrary.Sql {

    public class LinqUtil {

        public static TableAttribute GetTableAttribute<T>() {
            var customAttributes = Attribute.GetCustomAttributes(typeof (T));
            if (CollectionUtil.IsEmpty(customAttributes)) {
                throw new ArgumentException(typeof(T).Name + " is not mapped to a table.");
            }

            var ta = customAttributes.SingleOrDefault(a => a is TableAttribute);
            if (ta == null) {
                throw new ArgumentException(typeof(T).Name + " is not mapped to a table.");
            }

            return ta as TableAttribute;
        }

        public static List<ColumnAttribute> GetColumnAttributes<T>() {
            return GetColumnProperties<T>().Select(GetColumnAttribute).ToList();
        }

        public static ColumnAttribute GetColumnAttribute(PropertyInfo property) {
            var customAttributes = property.GetCustomAttributes(true);
            if (CollectionUtil.IsEmpty(customAttributes)) {
                throw new ArgumentException(property.Name + " is not mapped to a column.");
            }

            var columnAttribute  = customAttributes.SingleOrDefault(attribute => attribute is ColumnAttribute);
            if (columnAttribute == null) {
                throw new ArgumentException(property.Name + " is not mapped to a column.");
            }

            return columnAttribute as ColumnAttribute;
        }

        public static List<PropertyInfo> GetColumnProperties<T>() {
            var type = typeof(T);
            var columnProperties = type.GetProperties(BindingFlags.Public 
                                                    | BindingFlags.Instance
                                                    | BindingFlags.GetProperty
                                                    | BindingFlags.SetProperty)
            .Where(propertyInfo =>
                propertyInfo.GetCustomAttributes(true)
                            .SingleOrDefault(attribute => attribute is ColumnAttribute) != null
            ).ToList();

            if (CollectionUtil.IsEmpty(columnProperties)) {
                throw new ArgumentException(typeof(T).Name + " does not have columns.");
            }
            
            return columnProperties;
        }

        public static DataTable MapToDataTable<T>(IEnumerable<T> entities) {
            var table = new DataTable();

            var properties = GetColumnProperties<T>();
            properties.ForEach(propertyInfo => {
                var propertyType = propertyInfo.PropertyType;
                if (propertyType.IsGenericType &&
                    propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }
                table.Columns.Add(new DataColumn(propertyInfo.Name, propertyType));
            });

            entities.ToList().ForEach(entity => {
                var row = table.NewRow();
                foreach (var pi in properties) {
                    row[pi.Name] = pi.GetValue(entity, null) ?? DBNull.Value;
                }
                table.Rows.Add(row);
            });

            return table;
        }
    }
}
